(ns rete
  (:require [clojure.xml :as xml])
  (:require [clojure.zip :as zip])
  (:require [clojure.set :as s]) 
  (:require [clojure.string :as string]) 
  (:require [clojure.java.io :as io])
  (:require [clojure.contrib.zip-filter.xml :as zf]))

 
;DEFINICIONES

(def reteNet (ref {}))            ; Map principal, contiene el acceso a la red.
(def workingMemory (ref #{}))     ; Se define un Hash Set porque no tiene duplicados.
(def actualLeftHandSide (ref {})) ; Map de Condiciones
(def actualRightHandSide (ref{})) ; Map de Conclusiones
(def newFacts (ref #{}))          ; Map de nuevos hechos

;FUNCIONES

; loadNode ( node id key value )
; params:
; node : referencia al objeto nodo
; id : Valor del identificador del nodo
; key : Nombre del atributo
; value : Valor del atributo
(defn loadNode [_node _id _key value]
  (. _node setName _id)
  (. _node setKey _key)
  (. _node setValue value)
  )

;//////////////////////////////

(defn getNode [coll nodo]
  (def nodeAux (ref nil))
  (doseq [x coll]
    (if (. x equals nodo)
      (dosync (ref-set nodeAux x))
      )
    )
  @nodeAux  
  )

;//////////////////////////////

(defn createBethaMemory [collLHS collRHS]  
  (def lastBNode (core.BethaNode.))  
  (doseq [lhs (seq collLHS)]   
    ;Construyo las memorias Beta, como supongo que no pueden haber variables en 
    ; una regla. Las voy conectando de acuerdo al orden que tienen en el Map
    (if (< (. lastBNode getParentSize) 2)      
      (. (first (second lhs)) linkBethaMemory lastBNode)   
      (do        
        (def lastWBNode (. lastBNode getWorkingBethaNode))
        (def lastBNode (core.BethaNode.))
        (. lastWBNode addBethaNode lastBNode)
        (. (first (second lhs)) linkBethaMemory lastBNode)
       )
     )    
  )
  (let [lastWBNode (. lastBNode getWorkingBethaNode)]
  (doseq [rhs (seq collRHS)]
    ;Agrego las Conclusiones de la Regla al WorkingBethaNode que sea Hoja (p-node)
    (. lastWBNode addRHS (second rhs))
    )
  )
)

(defn addFact[nodo]
  ;(println nodo)
  (dosync
    (if-not (some #(= nodo %) @workingMemory)
      ;(println "El nodo no esta " nodo)
      (do 
        (ref-set workingMemory (conj @workingMemory nodo)); agrego al working memory.
        ;(println "@workingmemory" @workingMemory) 
        (let [alphaSet (get @reteNet (. nodo getKey))];retorna el elemento del map que tiene esa clave
          (if-not (nil? alphaSet)            
            (doseq [x alphaSet]
              ;(println "alphaset" x)
              (if (. x equals nodo)
                (let [listaRHS (. x updateState (. nodo getValue))]; que pasa aca?
                  (doseq [nodoRHS listaRHS]                           
                   ; (println "lista RHS"listaRHS)
                   ; (println "NodoRHS" nodoRHS)
                    ;(def coll (clojure.set/union coll nodoRHS))              
                    (addFact nodoRHS) 
                    )
                  )
                )          
              )
            )
          )   
        )
      )
    )
  )

;//////////////////////////////

(defn runEngine [xml]
 ;(println xml)
 ;(def newFacts (ref #{}))
  ;Xml parser
  (doseq [mysymbol (xml-seq xml) :when (= :symbol (:tag mysymbol))] ;tomo el <symbol> del xml
    ;(println "mysymbol:" mysymbol)
    (dosync
        (doseq [ myfact (:content mysymbol) :when (= :fact (:tag myfact))]  ;tomo los hechos de <facts>
           ;(println "myfact:" myfact)    
           ( def myfactvalue (:value (:attrs myfact)) )
           ; (println "myfactvalue" myfactvalue )
           ; (println "myfact name" (:name (:attrs myfact) )
           (def nodo (core.Node.))
           (loadNode nodo "input" (:name (:attrs myfact)) (:value (:attrs myfact)) )            
           (addFact nodo)
           ;(println "addFact: " (. nodo toString))
           ;(println "Working Memory : " (. @rete/workingMemory toString))
        )
    ; chequeo si en el working memory existen nuevos hechos.
    ; si hay los muestro
    (doseq [x (. (. @rete/workingMemory toString) split "#")]
        (if (> (.indexOf x "rule") 0)
           (println x)
        ))
      
    ;Guardo en archivo      
    ;(with-open [wrtr (io/writer "output.txt")]
    ;(.write wrtr xml_output))
    
    
    )
    )
  
      
)


 ;//////////////////////////////

(defn addCondition [_node ]
  ;Deja la condicion en @actualLeftHandSide y chequea contra @reteNet     
  (let [alpha _node
        _key   (. alpha getKey)
        _value (. alpha getValue)
        _id    (. alpha getName)
        ]       
    ;(if (re-find #"^[0-9]+$" _value) ;Si la regla tiene seteado un numero (constante)
      
      ;(loadNode alpha _id _key _value)	              
      (dosync
        (if (contains? @reteNet _key)
          ; Agrego al Set el AlphaNode actual, ya que se encontro una entrada en reteNet
          (let [actualSet (get @reteNet _key)
                actualNode (getNode actualSet alpha)]          
            ; si la condicion esta en el actualSet => devuelvo la misma instancia
            ; sino hago lo que sigue 
            ; (println "Existe la entrada")
            ; (println "actual set" actualSet)
            (def finalSet (ref #{}))
            (if-not (nil? actualNode)   
              (do
                ; si existe debo moverlo al comienzo del set                
                (dosync
                  (doseq [x actualSet]
                    (if-not (. x equals actualNode)
                      (ref-set finalSet (cons x @finalSet)) ; coloca el nodo que existe al principio del set
                    )
                  )
                  (ref-set finalSet (cons actualNode @finalSet)); agrega al principio
                )
                
                ;(println "final set: "finalSet)
                ;(println "actualNode "actualNode)
                
                )
              (do
                (dosync (ref-set finalSet (cons alpha actualSet)) )  ; agrego alpha al comienzo del set
                ;(println "finalSet Nuevo Nodo:"finalSet)
                )
              )
            
            (def finalSetNRef @finalSet)
            ;(println "finalSetNRef:" finalSetNRef)
            
            (ref-set actualLeftHandSide (merge (deref actualLeftHandSide) {_key finalSetNRef}))    
            ;(println "LHS " @actualLeftHandSide)
            )                    
          (do  ;Agrego un clave=valor nuevo
            
            (let [newMap {_key #{alpha}}]
              (ref-set actualLeftHandSide (merge (deref actualLeftHandSide) newMap))
              ;(println "No existe la entrada")
              )
            )
          )
        )
      ;)
    ) 
 )

;//////////////////////////////

(defn addAction [_id _key _value]
  (let [nodo (core.Node.)]
    (loadNode nodo _id _key _value)
    (dosync (ref-set actualRightHandSide (merge (deref actualRightHandSide) {_key nodo})))
    )
  )

;//////////////////////////////

(defn addWme [ wme ]
  (println wme)  
)


;Crea la red Rete desde el xml con las reglas.      
(defn createNet [xml]
  ;(println "Create Rete Net empty")
  (dosync ( ref-set reteNet (hash-map)))
  
  ;Arma el conjunto de acciones (RHS)
  ;(println "Create RHS set")
  ; Xml Parser
  (doseq [myrule (xml-seq xml) :when (= :rule (:tag myrule))] ;tomo el <rule> del xml
    ;(println "myrule:" myrule)
    (dosync (ref-set actualRightHandSide {})
        (doseq [ myaction (:content myrule) :when (= :then (:tag myaction))]  ;tomo las acciones en <then>
          ;(println "myaction:" myaction)
          (doseq [ myatt (:content myaction) :when (= :attr (:tag myatt))]  ; tomo el <attr>      
            ( def myactionvalue (first (:content myatt)) )
              ;(println "myactionvalue" myactionvalue )
              (addAction (:id (:attrs myrule)) (:name (:attrs myatt)) myactionvalue ) ;Right-Hand Side
          )
        )
        ;(println "RHS:" @actualRightHandSide )

        ;Arma el conjunto condiciones (LHS)
        ;(println "Create Conditions set (LHS)")
        (doseq [mycondition (:content myrule) :when (= :if (:tag mycondition))]  ; tomo las condiciones de <if>           
          (dosync (ref-set actualLeftHandSide {})
          (doseq [myattr (:content mycondition) :when (= :attr (:tag myattr))]  ; tomo los atributos de <attr>
            ;Se crea un alpha node para cada condicion
            (def alphaNode (core.AlphaNode.))
              (loadNode alphaNode (:id (:attrs myrule)) (:name (:attrs myattr)) (first (:content myattr)))                
                (addCondition alphaNode)  ;Left-hand Side          
                
            )           
         ;(println "Conditions (LHS):" @actualLeftHandSide )
          
         
           (createBethaMemory @actualLeftHandSide @actualRightHandSide)          
            ;(println "Creating BethaMemory")
            ;(println reteNet)
            (ref-set reteNet (merge @reteNet (deref actualLeftHandSide)))  ;deref y @ hacen lo mismo
            ;(println reteNet)
            )
          )
        )
      )  
    )

;//////////////////////////////

(defn updateRete [alphaNode value]
  (let [workingAlpha (. alphaNode getWorkingAlphaNode)]
    (. alphaNode updateState value)    
    )
  )

;//////////////////////////////


(defn printRete [coll]
  (doseq [x (seq coll)]
    (doseq [y (seq (second x))]
      (println  y)
      )
    )
  )
